package jp.bluecab.bluecabsystem.gps;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import jp.bluecab.bluecabsystem.util.SdLog;
import jp.bluecab.bluecabsystem.widget.AppWidget;

import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;

import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.hardware.GeomagneticField;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.Vibrator;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.Toast;

public class GPSService extends Service {
	
	private static final String CLASSNAME = GPSService.class.getName();
	
	private LocationManager mLocationManager;
	private Context mApplicationContext;
	private Calendar mLastUpdateTime = Calendar.getInstance();
	private Handler mHandler;
	
	final RemoteCallbackList<IUpdateLocationCallback> callbackList = new RemoteCallbackList<IUpdateLocationCallback>();
	
	@Override
	public void onCreate() {
		Log.v(CLASSNAME, "onCreate() called");
		initGPSDevice();
	}
	
	private final String BUTTON_CLICK_ACTION_OP = "BUTTON_CLICK_ACTION_OP";
	private final String BUTTON_CLICK_ACTION_REST = "BUTTON_CLICK_ACTION_REST";
	private final String BUTTON_CLICK_ACTION_OUT = "BUTTON_CLICK_ACTION_OUT";
	
	private final int OP_MODE_OP = 1;
	private final int OP_MODE_REST = 2;
	private final int OP_MODE_OUT = 3;
	private int op_mode = OP_MODE_OUT; // デフォルト
	private boolean op_changed = false;
	
	@Override
	public void onStart(Intent intent, int startId) {
		Log.v(CLASSNAME, "onStart() called");
		super.onStart(intent, startId);
		
		ComponentName widget = new ComponentName(this, AppWidget.class);
		RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.widget);
		AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
		
		if ("FIRST_ACTION".equals(intent.getAction())) {
			// ウィジェットを画面に貼り付けたとき
			setImageResource(remoteViews);
		} else {
			// ウィジェットのボタンを押下したとき
			btnClicked(remoteViews, intent.getAction());
		}
		
		// ボタンクリックでPendingIntentによりonStartサービスが発動するよう設定する
		Intent intent_btn_op = new Intent();
		intent_btn_op.setAction(BUTTON_CLICK_ACTION_OP);
		PendingIntent pi_op = PendingIntent.getService(this, 0, intent_btn_op, 0);
		remoteViews.setOnClickPendingIntent(R.id.btnimage_op, pi_op);
		
		Intent intent_btn_rest = new Intent();
		intent_btn_rest.setAction(BUTTON_CLICK_ACTION_REST);
		PendingIntent pi_rest = PendingIntent.getService(this, 0, intent_btn_rest, 0);
		remoteViews.setOnClickPendingIntent(R.id.btnimage_rest, pi_rest);
		
		Intent intent_btn_out = new Intent();
		intent_btn_out.setAction(BUTTON_CLICK_ACTION_OUT);
		PendingIntent pi_out = PendingIntent.getService(this, 0, intent_btn_out, 0);
		remoteViews.setOnClickPendingIntent(R.id.btnimage_out, pi_out);
		
		// widgetのイメージタイトルクリックイベントに呼び出したいActivityをIntentに設定する
		Intent imgintent = new Intent(this, MainActivity.class);
		// imgintent.addCategory(Intent.CATEGORY_LAUNCHER);
		// imgintent.setAction(Intent.ACTION_MAIN);
		// imgintent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED |
		// Intent.FLAG_ACTIVITY_NEW_TASK);
		PendingIntent img_pending = PendingIntent.getActivity(this, 0, imgintent, 0);
		remoteViews.setOnClickPendingIntent(R.id.widgetimage, img_pending);
		
		// widgetの更新
		appWidgetManager.updateAppWidget(widget, remoteViews);
	}
	
	// ボタンクリック時に呼ばれる
	private void btnClicked(RemoteViews view, String key) {
		if (BUTTON_CLICK_ACTION_OP.equals(key)) {
			op_mode = OP_MODE_OP;
			
		} else if (BUTTON_CLICK_ACTION_REST.equals(key)) {
			op_mode = OP_MODE_REST;
			
		} else if (BUTTON_CLICK_ACTION_OUT.equals(key)) {
			op_mode = OP_MODE_OUT;
		}
		
		if (op_mode < OP_MODE_OUT) {
			startLocationListener();
		}
		if (op_mode > 0) {
			op_changed = true;
			setImageResource(view);
			vibrate();
			updateLocation(cLocation);
		}
		
	}
	
	// 画像のイメージ変更
	private void setImageResource(RemoteViews view) {
		switch (op_mode) {
		case OP_MODE_OP:
			view.setImageViewResource(R.id.btnimage_op, R.drawable.button_op_on);
			view.setImageViewResource(R.id.btnimage_rest, R.drawable.button_rest_off);
			view.setImageViewResource(R.id.btnimage_out, R.drawable.button_out_off);
			break;
		case OP_MODE_REST:
			view.setImageViewResource(R.id.btnimage_rest, R.drawable.button_rest_on);
			view.setImageViewResource(R.id.btnimage_op, R.drawable.button_op_off);
			view.setImageViewResource(R.id.btnimage_out, R.drawable.button_out_off);
			break;
		case OP_MODE_OUT:
			view.setImageViewResource(R.id.btnimage_out, R.drawable.button_out_on);
			view.setImageViewResource(R.id.btnimage_op, R.drawable.button_op_off);
			view.setImageViewResource(R.id.btnimage_rest, R.drawable.button_rest_off);
			break;
		}
	}
	
	// バイブレイト！
	private void vibrate() {
		if (op_mode > 0) {
			Vibrator vib = (Vibrator) getSystemService(VIBRATOR_SERVICE);
			if (op_mode == OP_MODE_OP) {
				long[] ptn = { 100, 200 };// ブ
				vib.vibrate(ptn, -1);
			} else if (op_mode == OP_MODE_REST) {
				long[] ptn = { 100, 200, 100, 200 };// ブッブッ
				vib.vibrate(ptn, -1);
			} else {
				long[] ptn = { 100, 400 };// ブー
				vib.vibrate(ptn, -1);
			}
			
		}
	}
	
	private void initGPSDevice() {
		// SdLog.put("start");
		
		// locationManagerの取得
		mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		
		// 最適なGPS取得方法を選択
		Criteria criteria = new Criteria();
		// 精度を指定します
		// criteria.setAccuracy(Criteria.ACCURACY_FINE);// Accuracy
		// は設定しないのがベストプラクティス
		// 高度を取得するかどうかを指定します
		criteria.setAltitudeRequired(false);
		// 進行方向を取得するかどうかを指定します
		criteria.setBearingRequired(true);
		// 位置情報の取得に費用がかかることを許可するかどうかを指定します
		criteria.setCostAllowed(true);
		// スピードを取得するかどうかを指定します
		criteria.setSpeedRequired(true);
		// 消費電力量を指定します
		// criteria.setPowerRequirement(Criteria.POWER_LOW);// PowerRequirement
		// は設定しないのがベストプラクティス
		
		// getBestProvider メソッドを使うと正しくプロバイダが取得できない
		String provider = mLocationManager.getBestProvider(criteria, true);
		
		if (provider == null) { // GPSがOFFの時
			showProviderSetting();
			return;
		}
		
		mHandler = new Handler(Looper.getMainLooper());
		mApplicationContext = this.getApplicationContext();
		updateLastUpdateTime();
		int freq = AppPreferences.getUpdateFreq(mApplicationContext);
		mLastUpdateTime.add(Calendar.SECOND, -freq);
		
		// 最後に取得した位置情報を取得
		// Location location = mLocationManager.getLastKnownLocation(provider);
		
		// 現在位置の変化で呼び出されるメソッドを登録
		SdLog.put("Call updateLocation ByINIT");
		startLocationListener();
		
		// 方位センサー
		mRegisteredSensor = false;
		mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
		mSensorAdaptor = new OrientationSensorAdaptor();
		
		List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ORIENTATION);
		
		if (sensors.size() > 0) {
			Sensor sensor = sensors.get(0);
			mRegisteredSensor = mSensorManager.registerListener(mSensorAdaptor, sensor, SensorManager.SENSOR_DELAY_FASTEST);
		}
	}
	
	private void startLocationListener() {
		// 省電力のために通知の制限をする
		// どんなに変化しても30秒より短い間隔、10M以下の位置の変化は通知しない
		mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1 * 30 * 1000, 10, locationListener);
		
	}
	
	private void updateLastUpdateTime() {
		mLastUpdateTime = Calendar.getInstance();
	}
	
	private void showProviderSetting() {
		// 位置情報が有効になっていない場合は、Google Maps アプリライクな [現在地機能を改善] ダイアログを起動
		new AlertDialog.Builder(this).setTitle("現在地機能を設定").setMessage("現在、位置情報は一部有効ではないものがあります。次のように設定すると、もっともすばやく正確に現在地を検出できるようになります:\n\n● 位置情報の設定でGPSとワイヤレスネットワークをオンにする\n\n● Wi-Fiをオンにする")
				.setPositiveButton("設定", new DialogInterface.OnClickListener() {
					@Override
					public void onClick(final DialogInterface dialog, final int which) {
						// 端末の位置情報設定画面へ遷移
						try {
							startActivity(new Intent("android.settings.LOCATION_SOURCE_SETTINGS"));
						} catch (final ActivityNotFoundException e) {
							// 位置情報設定画面がない糞端末の場合は、仕方ないので何もしない
						}
					}
				}).setNegativeButton("スキップ", new DialogInterface.OnClickListener() {
					@Override
					public void onClick(final DialogInterface dialog, final int which) {
					} // 何も行わない
				}).create().show();
		
		return;
	}
	
	private Location cLocation;
	private final LocationListener locationListener = new LocationListener() {
		public void onLocationChanged(Location location) {
			
			if (location == null) return;
			
			SdLog.put("onLocationChanged");
			cLocation = location;
			
			// コールバック関数に現在地を通知
			updateLocationCallbackFunc(location.getLatitude(), location.getLongitude(), location.getAltitude());
			
			int freq = AppPreferences.getUpdateFreq(mApplicationContext);
			Calendar tmpTime = Calendar.getInstance();
			tmpTime.setTime(mLastUpdateTime.getTime());
			tmpTime.add(Calendar.SECOND, freq);
			Calendar currentTime = Calendar.getInstance();
			
			if (op_changed || currentTime.compareTo(tmpTime) >= 0) {
				SdLog.put("Call updateLocation ByLOC_CHANGE");
				updateLocation(location);
			} else {
				SdLog.put("TimeIsFirst!!!!!!!!");
				SdLog.put("tmptime:" + tmpTime.get(Calendar.MINUTE) + "mm." + tmpTime.get(Calendar.SECOND) + "ss.");
				SdLog.put("nowtime:" + currentTime.get(Calendar.MINUTE) + "mm." + currentTime.get(Calendar.SECOND) + "ss.");
			}
		}
		
		public void onProviderDisabled(String provider) {
		}
		
		public void onProviderEnabled(String provider) {
		}
		
		// あまりあてにならない
		// プロバイダが使用不能になるまで呼び出されない
		public void onStatusChanged(String provider, int status, Bundle extras) {
		}
		
	};
	
	private void updateLocation(Location location) {
		if (location == null) {
			SdLog.put("location == null");
			return;
		}
		if (op_changed == false && op_mode == OP_MODE_OUT) {// 業務終了して一度アップしたら以降しない
			return;
		}
		SdLog.put("updateLocation CALLED");
		
		cLocation = location;
		updateLastUpdateTime();
		
		MyRunnable myRunnable = new MyRunnable(location);
		Thread updateThread = new Thread(null, myRunnable, "refresh_updateLocation");
		updateThread.start();
		
	}
	
	class MyRunnable implements Runnable {
		
		Location mLocation;
		
		public MyRunnable(Location location) {
			mLocation = location;
		}
		
		@Override
		public void run() {
			updateLocation2(mLocation);
		}
		
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		
		if (IGPSService.class.getName().equals(intent.getAction())) {
			return gpsServiceIf;
		}
		return null;
	}
	
	private IGPSService.Stub gpsServiceIf = new IGPSService.Stub() {
		// 呼ばずにアクティビティを終了すると挙動がおかしくなる
		public void stopService() throws RemoteException {
			mLocationManager.removeUpdates(locationListener);
			
			if (mRegisteredSensor) {
				mSensorManager.unregisterListener(mSensorAdaptor);
				mRegisteredSensor = false;
			}
		}
		
		public void registerUpdateLocationCallback(IUpdateLocationCallback cb) throws RemoteException {
			if (cb != null) {
				callbackList.register(cb);
			}
		}
		
		public void unreigsterUpdateLocationCallback(IUpdateLocationCallback cb) throws RemoteException {
			if (cb != null) {
				callbackList.register(cb);
			}
			
			if (mRegisteredSensor) {
				mSensorManager.unregisterListener(mSensorAdaptor);
				mRegisteredSensor = false;
			}
		}
	};
	
	/*
	 * 位置情報更新
	 */
	private void updateLocation2(Location location) {
		// SdLog.put("start");
		
		double lat = location.getLatitude();
		double lon = location.getLongitude();
		double alt = location.getAltitude();
		
		// コールバック関数に現在地を通知
		// updateLocationCallbackFunc(lat, lon, alt);
		
		Log.d(CLASSNAME, "location(" + lat + "," + lon + "," + alt + ")");
		
		// 送信先サーバのURLの設定
		String serverUrl = AppPreferences.getServerUrl(this.getApplicationContext());
		if (serverUrl.length() == 0) {
			return;
		}
		
		// ユーザIDの設定
		String loginId = AppPreferences.getLoginId(this.getApplicationContext());
		
		// URLを設定
		String postUrl = "http://" + serverUrl + "/api";
		
		HttpParams params = new BasicHttpParams();
		params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
		
		// タイムアウトを設定
		HttpConnectionParams.setConnectionTimeout(params, 10000);
		HttpConnectionParams.setSoTimeout(params, 8000);
		
		// POSTメソッド設定
		HttpClient httpclient = new DefaultHttpClient(params);
		HttpPost httppost = new HttpPost(postUrl);
		
		try {
			// 送信するパラメータを設定
			List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
			
			nameValuePairs.add(new BasicNameValuePair("loginid", loginId));
			nameValuePairs.add(new BasicNameValuePair("latitude", Double.toString(lat)));
			nameValuePairs.add(new BasicNameValuePair("longitude", Double.toString(lon)));
			nameValuePairs.add(new BasicNameValuePair("orientation", Double.toString(orientation)));
			nameValuePairs.add(new BasicNameValuePair("op_mode", String.valueOf(op_mode)));
			
			httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs, HTTP.UTF_8));
			HttpResponse response = httpclient.execute(httppost);
			response.getEntity();
			
			SdLog.put("SEND!!" + httppost.getMethod());
		} catch (SocketTimeoutException e) {
			SdLog.put(e);
			mHandler.post(new Runnable() {
				@Override
				public void run() {
					Context context = getApplicationContext();
					Toast toast = Toast.makeText(context, R.string.cannot_connect_server, Toast.LENGTH_LONG);
					toast.show();
				}
			});
			
		} catch (IOException e) {
			SdLog.put(e);
			mHandler.post(new Runnable() {
				@Override
				public void run() {
					Context context = getApplicationContext();
					Toast toast = Toast.makeText(context, R.string.io_error, Toast.LENGTH_LONG);
					toast.show();
				}
			});
			
		} finally {

		}
		
		op_changed = false;
		
		if (op_mode == OP_MODE_OUT) {
			mLocationManager.removeUpdates(locationListener);
		}
	}
	
	private void updateLocationCallbackFunc(double latitude, double longitude, double altitude) {
		
		int n = callbackList.beginBroadcast();
		try {
			for (int i = 0; i < n; i++) {
				callbackList.getBroadcastItem(i).updateLocation(latitude, longitude, altitude);
			}
		} catch (RemoteException e) {
			Log.e(CLASSNAME, "RemoteException:" + e.getMessage());
		}
		callbackList.finishBroadcast();
	}
	
	private boolean mRegisteredSensor;
	private SensorManager mSensorManager;
	protected OrientationSensorAdaptor mSensorAdaptor = null;
	private float orientation = 0.0f;// 回転角
	private GeomagneticField geomagnetic;
	
	/**
	 * センサーの値通知を受け取るリスナ
	 */
	private class OrientationSensorAdaptor implements SensorEventListener {
		private boolean first = true;// 最初の変化はスルー
		private float pre_o = 0;
		private Location pre_loc = new Location(LocationManager.GPS_PROVIDER);
		
		/**
		 * センサーの正確さに変化があったタイミングで通知される
		 * 
		 * @param sensor
		 *            センサー
		 * @param accuracy
		 *            正確さ
		 */
		@Override
		public void onAccuracyChanged(Sensor sensor, int accuracy) {
		}
		
		@Override
		public void onSensorChanged(SensorEvent event) {
			if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {
				
				if (cLocation == null) return;
				
				// 方位から回転角を設定
				float[] values = event.values;
				float ori = values[SensorManager.DATA_X];
				
				geomagnetic = new GeomagneticField(new Float(cLocation.getLatitude()), new Float(cLocation.getLongitude()), new Float(cLocation.getAltitude()), System.currentTimeMillis());
				orientation = ori + geomagnetic.getDeclination();
				
				// コールバック関数呼び出し
				// マップ再描画
				int n = callbackList.beginBroadcast();
				try {
					for (int i = 0; i < n; i++) {
						callbackList.getBroadcastItem(i).updateOrientation(orientation);
					}
				} catch (RemoteException e) {
					Log.e(CLASSNAME, "RemoteException:" + e.getMessage());
				}
				callbackList.finishBroadcast();
				
				// 方位角度の変化によってアップデート
				if (first) {
					pre_o = orientation;
					pre_loc.setLatitude(cLocation.getLatitude());
					pre_loc.setLongitude(cLocation.getLongitude());
					first = false;
					return;
				}
				// 前回変化より30度以上の変化で強制アップロード
				// 業務中のみかつ前回送信位置が異なるときのみ
				if (Math.abs(pre_o - orientation) > 30 && op_mode == OP_MODE_OP) {
					if (pre_loc.getLatitude() != cLocation.getLatitude() || pre_loc.getLongitude() != cLocation.getLongitude()) {
						SdLog.put("Call updateLocation ByORIENTATION");
						updateLocation(cLocation);
						
						// 前回情報保持
						pre_o = orientation;
						pre_loc.setLatitude(cLocation.getLatitude());
						pre_loc.setLongitude(cLocation.getLongitude());
					} else {
						// 場所に変化がないのでリターン
						return;
					}
				} else {
					// 角度が30度未満なのでリターン
					return;
				}
				
			}
		}
	}
}